﻿using System.Collections.Concurrent;
using MESTranslateToPLC.Schema.Common;
using NModbus;
using NModbus.Device;

namespace MESTranslateToPLC.Service;

using System.Net.Sockets;

public class ModbusService : IModbusMaster
{
    private TcpClient _client;
    private IModbusMaster _master;
    private static readonly ModbusFactory ModbusFactory = new();
    public bool IsConnected => _client.Connected;
    private readonly string _ipAddress = "127.0.0.1";
    private readonly int _port = 502;
    private readonly ConcurrentQueue<ModbusCommand> _commandQueue;
    private readonly Task _workerTask;
    private bool _isRunning;
    public readonly CancellationTokenSource cancellationTokenSource;
    private readonly Timer _reconnectTimer;
    private SemaphoreSlim _semaphore = new(1, 1); // 防止回调并发
    private readonly ILogger<ModbusService> _logger;
    private string Key => $"{_ipAddress}:{_port}";

    public ModbusService(string ipAddress, int port
        // ,Action<ModbusCommand>? processCommand = null
    )
    {
        _ipAddress = ipAddress;
        _port = port;
        _client = new TcpClient();
        _logger = new LoggerFactory().CreateLogger<ModbusService>();
        _commandQueue = new ConcurrentQueue<ModbusCommand>();
        _isRunning = true;
        cancellationTokenSource = new CancellationTokenSource();
        _reconnectTimer = new Timer(async _ =>
        {
            await _semaphore.WaitAsync();
            try
            {
                await CheckConnectionAsync();
            }
            finally
            {
                _semaphore.Release();
            }
        }, null, TimeSpan.Zero, TimeSpan.FromMilliseconds(200));
        // processCommand ??= async (common) => { };
        _workerTask = Task.Run(async () =>
        {
            while (_isRunning)
            {
                if (_commandQueue.Count > 0)
                {
                    if (_commandQueue.TryDequeue(out var command))
                    {
                        await ProcessCommand(command);
                    }
                }
                else
                {
                    Thread.Yield();
                }
            }
        }, cancellationTokenSource.Token);
    }

    private async Task CheckConnectionAsync()
    {
        try
        {
            if (IsConnected)
            {
                await ReadCoilsAsync(0, 1, 1);
            }
            else
            {
                await Connect();
                _logger.LogInformation("{0}:Modbus恢复连接", Key);
            }
        }
        catch (Exception e)
        {
            _logger.LogWarning("{0}:Modbus连接异常:{1}", Key, e.Message);
        }
    }

    public async Task ProcessCommand(ModbusCommand command)
    {
    }

    public bool SendCommand(ModbusCommand command)
    {
        if (IsConnected)
        {
            _commandQueue.Enqueue(command);
            return true;
        }

        return false;
    }

    public bool StopReconnect()
    {
        try
        {
            _reconnectTimer.Change(Timeout.Infinite, Timeout.Infinite);
            return true;
        }
        catch (Exception e)
        {
            return false;
        }
    }

    public bool StartReconnect()
    {
        try
        {
            _reconnectTimer.Change(TimeSpan.Zero, TimeSpan.FromMinutes(1));
            return true;
        }
        catch (Exception e)
        {
            return false;
        }
    }

    public async Task<bool> Connect(string? ipAddress = null, int? port = null)
    {
        if (!IsConnected)
        {
            try
            {
                _client = new TcpClient();
                _master = ModbusFactory.CreateMaster(_client);
                await _client.ConnectAsync(_ipAddress, _port);
            }
            catch (Exception ex)
            {
                // Console.WriteLine("Unable to connect to server " + ex.ToString());
                return false;
            }
        }

        return true;
    }

    public void Disconnect()
    {
        _isRunning = false;
        _workerTask.Wait();
        _master?.Dispose();
        _client.Close();
    }

    public void Dispose()
    {
        Disconnect();
    }

    public bool[] ReadCoils(byte slaveAddress, ushort startAddress, ushort numberOfPoints)
    {
        return _master.ReadCoils(slaveAddress, slaveAddress, numberOfPoints);
    }

    public async Task<bool[]> ReadCoilsAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints)
    {
        return await _master.ReadCoilsAsync(slaveAddress, slaveAddress, numberOfPoints);
    }

    public bool[] ReadInputs(byte slaveAddress, ushort startAddress, ushort numberOfPoints)
    {
        return _master.ReadInputs(slaveAddress, startAddress, numberOfPoints);
    }

    public async Task<bool[]> ReadInputsAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints)
    {
        return await _master.ReadInputsAsync(slaveAddress, startAddress, numberOfPoints);
    }

    public ushort[] ReadHoldingRegisters(byte slaveAddress, ushort startAddress, ushort numberOfPoints)
    {
        return _master.ReadHoldingRegisters(slaveAddress, startAddress, numberOfPoints);
    }

    public async Task<ushort[]> ReadHoldingRegistersAsync(byte slaveAddress, ushort startAddress,
        ushort numberOfPoints)
    {
        return await _master.ReadHoldingRegistersAsync(slaveAddress, startAddress, numberOfPoints);
    }

    public ushort[] ReadInputRegisters(byte slaveAddress, ushort startAddress, ushort numberOfPoints)
    {
        return _master.ReadInputRegisters(slaveAddress, startAddress, numberOfPoints);
    }

    public async Task<ushort[]> ReadInputRegistersAsync(byte slaveAddress, ushort startAddress,
        ushort numberOfPoints)
    {
        return await _master.ReadInputRegistersAsync(slaveAddress, startAddress, numberOfPoints);
    }

    public void WriteSingleCoil(byte slaveAddress, ushort coilAddress, bool value)
    {
        _master.WriteSingleCoil(slaveAddress, coilAddress, value);
    }

    public async Task WriteSingleCoilAsync(byte slaveAddress, ushort coilAddress, bool value)
    {
        await _master.WriteSingleCoilAsync(slaveAddress, coilAddress, value);
    }

    public void WriteSingleRegister(byte slaveAddress, ushort registerAddress, ushort value)
    {
        _master.WriteSingleRegister(slaveAddress, registerAddress, value);
    }

    public async Task WriteSingleRegisterAsync(byte slaveAddress, ushort registerAddress, ushort value)
    {
        await _master.WriteSingleRegisterAsync(slaveAddress, registerAddress, value);
    }

    public void WriteMultipleRegisters(byte slaveAddress, ushort startAddress, ushort[] data)
    {
        _master.WriteMultipleRegisters(slaveAddress, startAddress, data);
    }

    public async Task WriteMultipleRegistersAsync(byte slaveAddress, ushort startAddress, ushort[] data)
    {
        await _master.WriteMultipleRegistersAsync(slaveAddress, startAddress, data);
    }

    public void WriteMultipleCoils(byte slaveAddress, ushort startAddress, bool[] data)
    {
        _master.WriteMultipleCoils(slaveAddress, startAddress, data);
    }

    public async Task WriteMultipleCoilsAsync(byte slaveAddress, ushort startAddress, bool[] data)
    {
        await _master.WriteMultipleCoilsAsync(slaveAddress, startAddress, data);
    }

    public ushort[] ReadWriteMultipleRegisters(byte slaveAddress, ushort startReadAddress,
        ushort numberOfPointsToRead,
        ushort startWriteAddress, ushort[] writeData)
    {
        return _master.ReadWriteMultipleRegisters(slaveAddress, startReadAddress, numberOfPointsToRead,
            startWriteAddress, writeData);
    }

    public async Task<ushort[]> ReadWriteMultipleRegistersAsync(byte slaveAddress, ushort startReadAddress,
        ushort numberOfPointsToRead,
        ushort startWriteAddress, ushort[] writeData)
    {
        return await _master.ReadWriteMultipleRegistersAsync(slaveAddress, startReadAddress, numberOfPointsToRead,
            startWriteAddress, writeData);
    }

    public void WriteFileRecord(byte slaveAdress, ushort fileNumber, ushort startingAddress, byte[] data)
    {
        _master.WriteFileRecord(slaveAdress, fileNumber, startingAddress, data);
    }

    public TResponse ExecuteCustomMessage<TResponse>(IModbusMessage request) where TResponse : IModbusMessage, new()
    {
        return _master.ExecuteCustomMessage<TResponse>(request);
    }

    public IModbusTransport? Transport { get; }
}